// file:kenrel/fat32/fat32.c
// autor:jiang xinpeng
// time:2020.12.23
// copyright:(C) 2020-2050 by jiang xinpenng,All right are reserved.

#include <os/fatfs.h>
#include <os/fat.h>
#include <os/fsal.h>
#include <os/file.h>
#include <os/path.h>
#include <os/debug.h>
#include <os/fat.h>
#include <os/dir.h>
#include <os/walltime.h>
#include <os/memcache.h>
#include <lib/type.h>
#include <os/diskman.h>
#include <lib/errno.h>
#include <lib/stddef.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/status.h>
#include <lib/unistd.h>

//#define FATFS_LSEEK_DEBUG 
//#define FATFS_DEBUG

// fatfs extension
fatfs_extension_t fatfs_extension={0};

// fatfs sub table
char *fatfs_subtable[] =
    {
        "fat12",
        "fat16",
        "fat32",
        "exfat",
        NULL};

// check whether is a director
PRIVATE int FatFsIsDirector(char *path, dir_obj_t *dir)
{
    file_status_t res;
    res = FileOpenDir(dir, path); // open director
    if (res != FSTU_OK)
    {
        return -ENFILE;
    }
    return 0;
}

PRIVATE int FsalFatfsOpen(char *path, uint32_t flags)
{
    fsal_file_t *fp = NULL;
    file_obj_t *fobj = NULL;                  // file object point
    file_status_t stu;                        // file open status
    fatfs_file_extension_t *extension = NULL; // file extension
    char *p = path;
    uint32_t mode = 0;
    file_status_t res = FSTU_OK;
    int err;

    fp = FsalFileAlloc();
    if (!fp)
        return -ENOMEM;
    fp->extension = KMemAlloc(sizeof(fatfs_file_extension_t));
    if (!(fp->extension))
    {
        KPrint(PRINT_ERR "alloc file %s extension for open failed!\n", p);
        FsalFileFree(fp);
        return -ENOMEM;
    }
    // init file extension
    extension = fp->extension;
    extension->dir_path = NULL; // general file is NULL
    extension->temp_dir = NULL; // no temp dir
    memset(extension->path, 0, MAX_PATH_LEN);
    strcpy(extension->path, path); // set extension path
    fp->fsal = &fatfs_fsal;

    // set mode
    if (flags & O_RDONLY)
    {
        mode |= FA_READ;
    }
    else if (flags & O_WRONLY)
    {
        mode |= FA_WRITE;
    }
    else if (flags & O_RDWR)
    {
        mode |= FA_READ | FA_WRITE;
    }
    if (flags & O_EXCL)
    {
        mode |= FA_CREATE_NEW;
    }
    else if (flags & O_TRUNC)
    {
        mode |= FA_CREATE_ALWAYS;
    }
    else if (flags & O_APPEND)
    {
        mode |= FA_OPEN_APPEND;
    }
    else if (flags & O_CREATE)
    {
        mode |= FA_OPEN_ALWAYS;
    }

    if (flags & O_DIRECTORY) // open dir
    {
        err = FatFsIsDirector(path, &extension->dir);
        if (err < 0) // open dir failed
        {
            KMemFree(fp->extension);
            FsalFileFree(fp);
            return -ENOFILE;
        }
        else
        {
            extension->dir_path = extension->path; // open as director
        }
    }
    else
    {
        res = FileOpen(&extension->file, p, mode);
        if (res != FSTU_OK)
        {
            // try open as director
            err = FatFsIsDirector(path, &extension->dir);
            if (err < 0)
            {
                KMemFree(fp->extension);
                FsalFileFree(fp);
                return -ENOFILE;
            }
            else
            {
                extension->dir_path = extension->path; // open as director
            }
        }
    }
    return FSAL_FILE_FILE2IDX(fp);
}

PRIVATE int FsalFatfsClose(int idx)
{
    fsal_file_t *fp;
    fatfs_file_extension_t *extension;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    extension = fp->extension; // get file extension

    if (extension->dir_path)
    {
        FileCloseDir(&extension->dir); // close dir
        if (extension->temp_dir)
        {
            KMemFree(extension->temp_dir);
            extension->temp_dir = NULL;
        }
    }
    else
    {
        file_status_t res = FileClose(&extension->file); // close dir
        if (res != FSTU_OK)
        {
            KPrint(PRINT_ERR "[fatfs]: close file failed!\n");
            return -1;
        }
    }

    if (fp->extension)
    {
        KMemFree(fp->extension); // free extension
    }
    fp->extension = NULL;
    if (FsalFileFree(fp) < 0)
        return -1;
    return 0;
}

PRIVATE int FsalFatfsRead(int idx, char *buff, uint32_t size)
{
    fsal_file_t *fp;
    uint8_t *p = buff;
    uint32_t bytes = 0;


    if (FSAL_FILE_BAD_IDX(idx))
    {
        KPrint("[fatfs] read: idx err\n");
        return -1;
    }
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
    {
        KPrint("[fatfs] read: bad file\n");
        return -1;
    }

    file_status_t res = FileRead(&((fatfs_file_extension_t *)(fp->extension))->file, p, size, &bytes);
    if (res != FSTU_OK||bytes<0)
    {
        KPrint("[fatfs] file %d read failed,err:%d bytes %d\n", idx, res,bytes);
        return -1;
    }
    
    return bytes;
}

PRIVATE int  FsalFatfsWrite(int idx, char *buff, uint32_t size)
{
    fsal_file_t *fp;
    uint32_t bytes;
    file_status_t res;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;

    res = FileWrite(&((fatfs_file_extension_t *)(fp->extension))->file, buff, size, &bytes);
    if (res != FSTU_OK)
    {
        KPrint("[fatfs] write file return %d\n", res);
        return -1;
    }
    #ifdef FATFS_DEBUG
    KPrint("Write file return %d buff %s size %d\n",bytes,buff,size);
    #endif
    return bytes;
}

PRIVATE int FsalFatFsOpenDir(char *path)
{
    fsal_dir_t *dir = FsalDirAlloc();
    file_status_t res;

    if (!dir)
        return -1;

    dir->extension = KMemAlloc(sizeof(fatfs_dir_extension_t));
    if (!(dir->extension))
    {
        FsalDirFree(dir);
        return -ENOMEM;
    }
    dir->fsal = &fatfs_fsal;

    res = FileOpenDir(&((fatfs_dir_extension_t *)(dir->extension))->dir, path);
    if (res != FSTU_OK)
    {
        KMemFree(dir->extension);
        FsalDirFree(dir);
        return -1;
    }
    return FSAL_DIR2IDX(dir);
}

PRIVATE int FsalFatfsCloseDir(int idx)
{
    fsal_dir_t *dir;
    file_status_t res;
    if (FSAL_BAD_DIR_IDX(idx))
        return -1;
    dir = FSAL_IDX2DIR(idx);
    if (FSAL_BAD_DIR(dir))
        return -1;
    res = FileCloseDir(&((fatfs_dir_extension_t *)(dir->extension))->dir);
    if (res != FSTU_OK)
        return -1;
    if (dir->extension)
        KMemFree(dir->extension);
    dir->extension = NULL;
    if (FsalDirFree(dir) < 0)
        return -1;
    return 0;
}

PRIVATE int FsalFatfsChomod(char *path, mode_t mode)
{
    return -ENOSYS;
}

PRIVATE int FsalFatfsFChomod(int idx, mode_t mode)
{
    fsal_file_t *fp;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return 0;
}

PRIVATE int FsalFatfsFEof(int idx)
{
    fsal_file_t *fp;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return FileEof(&((fatfs_file_extension_t *)(fp->extension))->file);
}

PRIVATE int FsalFatfsFError(int idx)
{
    fsal_file_t *fp;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return FileError(&((fatfs_file_extension_t *)(fp->extension))->file);
}

PRIVATE offset_t FsalFatfsFTell(int idx)
{
    fsal_file_t *fp;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return FileTell(&((fatfs_file_extension_t *)(fp->extension))->file);
}

PRIVATE size_t FsalFatfsFSize(int idx)
{
    fsal_file_t *fp;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return FileSize(&((fatfs_file_extension_t *)(fp->extension))->file);
}

static int DoReadDir(dir_obj_t *dir, dirent_t *dire)
{
    file_status_t res;
    file_info_t info;

    res = FileReadDir(dir, &info);
    if (res != FSTU_OK)
        return -res;
    if (!(info.fname[0]))
        return -EPERM;
    // set dirent info
    dire->attr = 0;
    if (info.attr & ATT_RDONLY)
        dire->attr |= DIRENT_RDONLY;
    if (info.attr & ATT_HIDDEN)
        dire->attr |= DIRENT_HIDDEN;
    if (info.attr & ATT_SYS)
        dire->attr |= DIRENT_SYSTEM;
    if (info.attr & ATT_FDIR)
        dire->attr |= DIRENT_DIR;
    if (info.attr & ATT_ARCHIVE)
        dire->attr |= DIRENT_ARCHIVE;
    dire->size = info.size;
    dire->date = info.date;
    dire->time = info.time;
    memcpy(dire->name, info.fname, DIR_NAME_LEN);
    dire->name[DIR_NAME_LEN - 1] = '\0';
    return 0;
}

static int FsalFatfsReadDir(int idx, void *buff)
{
    fsal_dir_t *dir;
    fatfs_dir_extension_t *dirext;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    dir = FSAL_IDX2DIR(idx);
    if (!(dir->extension))
        return -1;
    dirext = dir->extension;
    return DoReadDir(&dirext->dir, buff);
}

static int FsalFatfsMkdir(char *path, mode_t mode)
{
    file_status_t res;
    res = FileMkDir(path);
    if (res != FSTU_OK)
    {
        return -1;
    }
    return 0;
}

static int FsalFatfsReName(char *old, char *new)
{
    file_status_t res;

    res = FileRename(old, new);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int FsalFatfsMkfs(const char *source, char *fstype, uint64_t flags)
{
    KPrint("fatfs: mkfs on source %s fstype %s\n",source,fstype);
    int solt = DiskSoltFindByPath((char *)source); // get device solt
    if (solt < 0)
    {
        KPrint("[fsal] %s: not found device %s!\n", __func__, source);
        return -1;
    }

    int pdrv, i;
    // translation to driver number
    for (i = 0; i < FS_VOLUME_MAX; i++)
    {
        if (driver_map[i] == solt)
        {
            break;
        }
    }
    if (i >= FS_VOLUME_MAX)
    {
        return -1.;
    }
    pdrv = i;

    char path[3] = {PDRV_TO_PATH(pdrv), ':', 0};
    file_status_t res;
    uint8_t work[FS_SEC_MAX];
    const uint8_t *p = path;
    mkfs_parm_t param = {FM_ANY, 0, 0, 0, 0};

    // set format fstype
    if (!strcmp(fstype, "fat12") || !strcmp(fstype, "fat16"))
    {
        param.fmt = FM_FAT;
    }
    else
    {
        if (!strcmp(fstype, "fat32"))
        {
            param.fmt = FM_FAT32;
        }
        else
        {
            if (!strcmp(fstype, "exfat"))
            {
                param.fmt = FM_EXFAT;
            }
        }
    }

    // make targe fstype
    res = FileMkFs(p, (mkfs_parm_t *)&param, work, sizeof(work));
    if (res != FSTU_OK)
    {
        KPrint("[fsal] %s: make fs on device %s failed!\n", __func__, source);
        return -1;
    }
    KPrint("fatfs: mkfs on driver %s ok\n",path);
    return 0;
}

static int DiskHasFs(uint8_t *buff)
{
    if (*(uint16_t *)(buff + SECTOR_SIZE - 2) = 0xaa55)
        return 1;
    return 0;
}

static int FsalFatfsMount(const char *source, const char *targe, char *fstype, uint64_t flags)
{
    KPrint("[fatfs] mount path %s\n", source);
    int solt = DiskSoltFindByPath((char *)source); // find volume
    if (solt < 0)
    {
        KPrint("not find device %s.\n", source);
        return -1;
    }
    // check whether device had map
    if (FsalPathFindDevice(source))
    {
        KPrint("device %s had been mount!\n", source);
        return -1;
    }
    // translation to pysical driver
    int pdrv, i;
    for (i = 0; i < FS_VOLUME_MAX; i++)
    {
        if (driver_map[i] == solt)
        {
            break;
        }
    }
    // get pysical driver
    pdrv = i;
    char path[3] = {PDRV_TO_PATH(pdrv), ':', 0};
    KPrint("[fatfs] mount driver path %s\n", path);

    // open device and read first sector
    int res = diskman.open(solt);
    if (res < 0)
    {
        KPrint("[fatfs] open device %s failed!\n", source);
        return -1;
    }
    uint8_t buff[SECTOR_SIZE];
    if (diskman.read(solt, 0, buff, SECTOR_SIZE) < 0)
    {
        KPrint("read %s failed!\n", source);
        diskman.close(solt);
        return -1;
    }
    diskman.close(solt);

    int format = 1;
    // disk whether had fs
    if (DiskHasFs(buff))
    {
        format = 0;
        // force format
        if (flags & MOUNT_FORMAT)
        {
            KPrint("[fatfs] will format disk\n");
            format = 1;
        }
    }
    else
    {
        if (!(flags & MOUNT_FORMAT))
        {
            KPrint("[fatfs] disk no fs!\n", source);
            return -1;
        }
    }
    // format disk
    const TCHAR *p;
    if (format)
    {
        uint8_t work[SECTOR_SIZE];
        p = (const TCHAR *)path;
        mkfs_parm_t parm = {FM_ANY, 0, 0, 0, 0};
        // determine fstype
        if (!strcmp(fstype, "fat12") || !strcmp(fstype, "fat16"))
        {
            parm.fmt = FM_FAT;
        }
        else
        {
            if (!strcmp(fstype, "fat32"))
            {
                parm.fmt = FM_FAT32;
            }
            else
            {
                if (!strcmp(fstype, "exfat"))
                {
                    parm.fmt = FM_EXFAT;
                }
            }
        }
        res = FileMkFs(p, &parm, work, sizeof(work));
        if (res != FSTU_OK)
        {
            KPrint("[fatfs] make fs on driver %s failed! return code=%d\n", source, res);
            return -1;
        }
        KPrint("[fatfs] mkfs on driver %s ok!\n", source);
    }
    // mount disk
    fatfs_t *fs;
    p = (const TCHAR *)path;
    if (!fatfs_extension.ref[pdrv]) // disk no mount
    {
        fs = KMemAlloc(sizeof(fatfs_t));
        if (!fs)
            return -1;
        memset(fs, 0, sizeof(fatfs_t));

        file_status_t res;
        uint8_t delayed = (flags | MOUNT_DELAYED); // delay mount
        res = FileMount(fs, p, delayed);
        if (res != FSTU_OK)
        {
            KPrint("[fatfs] mount %s failed! return code %d\n", source, res);
            KMemFree(fs);
            return -1;
        }
#if DEBUG_KERNEL
        KPrint("[fatfs] mount device %s to fs %s\n", __func__, source, fstype);
#endif
        fatfs_extension.fs[pdrv] = fs;
        fatfs_extension.mount_time[pdrv] = WALLTIME_WR_TIME(walltime.hour, walltime.minute, walltime.second);
        fatfs_extension.mount_data[pdrv] = WALLTIME_WR_DATE(walltime.year, walltime.month, walltime.day);
    }
    fatfs_extension.ref[pdrv]++; // inc mount ref
    // insert path to fsal
    if (FsalPathInsert(&fatfs_fsal, source, p, targe) < 0)
    {
#if DEBUG_KERNEL
        KPrint("%s: insert path %s[%s] to %s failed!\n", __func__, source, p, targe);
#endif
        // free fs
        if (fs)
            KMemFree(fs);
        return -1;
    }
    return 0;
}

static int FsalFatfsUnlink(char *path)
{
    file_status_t res;
    res = FileUnlink(path);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int FsalFatfsLseek(int idx, offset_t off, int whence)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    #ifdef FATFS_LSEEK_DEBUG
    KPrint("fatfs: lseek off %d idx %d whence %d\n",idx,off,whence);
    #endif
    offset_t new_off = 0;
    switch (whence)
    {
    case SEEK_SET:
        new_off = off;
        break;
    case SEEK_CUR:
        new_off = FileTell((file_obj_t *)fp->extension) + off;
        break;
    case SEEK_END:
        new_off = FileSize((file_obj_t *)fp->extension) + off;
        break;
    default:
        break;
    }
    file_status_t res;
    res = FileLSeek((file_obj_t *)fp->extension, new_off);
    if (res != FSTU_OK)
    {
        KPrint("[fatfs] lseek error\n");
        return -1;
    }
    #ifdef FATFS_LSEEK_DEBUG
    KPrint("fatfs: ftell %d fsize %d\n",FileTell((file_obj_t *)fp->extension),FileSize((file_obj_t *)fp->extension));
    #endif
    return new_off;
}

static int FsalFatfsFtruncate(int idx, offset_t off)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;

    offset_t old = FileTell((file_obj_t *)fp->extension);

    file_status_t res;
    res = FileLSeek((file_obj_t *)fp->extension, off);
    if (res != FSTU_OK)
        return -1;
    res = FileTruncate((file_obj_t *)fp->extension);
    if (res != FSTU_OK)
    {
        FileLSeek((file_obj_t *)fp->extension, old);
        return -1;
    }
    return 0;
}

static int FsalFatfsFsync(int idx)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    file_status_t res;
    res = FileSync((file_obj_t *)fp->extension);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int IsRootDir(char *path)
{
    char *p = path;
    int n = 0;
    while (*p)
    {
        switch (n)
        {
        case 0:
            if (*p >= '0' && *p <= '9')
                n++;
            break;
        case 1:
            if (*p == ':')
                n++;
            break;
        default:
            n = -1;
            break;
        }
        p++;
    }
    return n == 2;
}

static int FsalFatfsStatus(char *path, void *buff)
{
    file_status_t res;
    file_info_t finfo;

    if (IsRootDir(path))
    {
        int pdrv = PATH_TO_PDRV(path[0]);
        finfo.size = 0;
        finfo.date = fatfs_extension.mount_data[pdrv];
        finfo.time = fatfs_extension.mount_time[pdrv];
        finfo.attr = ATT_RDONLY | ATT_FDIR;
    }
    else
    {
        res = FileStatus(path, &finfo);
        if (res != FSTU_OK)
            return -EINVAL;
    }

    status_t *stat = (status_t *)buff;
    mode_t mode = STU_IREAD | STU_IWRITE;
    if (finfo.attr & ATT_RDONLY)
        mode &= ~STU_IWRITE;
    if (finfo.attr & ATT_FDIR)
        mode |= STU_IFDIR;
    else
        mode |= STU_IFREG;
    stat->st_mode = mode;
    stat->st_size = finfo.size;
    stat->st_atime = ((uint32_t)finfo.date << 16) | (uint32_t)finfo.time;
    stat->st_ctime = stat->st_mtime = stat->st_atime;
    return 0;
}

static int FsalFatfsAccess(const char *path, int mode)
{
    if (mode == F_OK)
    {
        file_status_t res;
        file_obj_t file;

        res = FileOpen(&file, path, FA_OPEN_EXISTING | FA_READ);
        if (res != FSTU_OK)
        {
            dir_obj_t dir;
            int ret=FatFsIsDirector(path,&dir);
            if(ret!=0)
                return -1;
            FileCloseDir(&dir);
            return 0;
        }
        FileClose(&file);
        return 0;
    }
    return -1;
}

static int FsalFatfsFstatus(int idx, void *buff)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -EINVAL;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (!fp)
        return -EINVAL;

    fatfs_file_extension_t *extension = (fatfs_file_extension_t *)fp->extension;
    char *path = extension->path;
    if (!path || !path[0])
        return -1;
    file_status_t res;
    file_info_t finfo;
    // sync file before fstat,not dir
    if (!extension->dir_path)
    {
        FileSync(&extension->file);
    }

    int pdrv = PDRV_TO_PATH(path[0]); // driver
    if (IsRootDir(path))
    {
        finfo.size = 0;
        finfo.date = fatfs_extension.mount_data[pdrv];
        finfo.time = fatfs_extension.mount_time[pdrv];
        finfo.attr = ATT_RDONLY | ATT_FDIR;
    }
    else
    {
        res = FileStatus(path, &finfo);
        if (res != FSTU_OK)
            return -1;
    }

    status_t *stat = (status_t *)buff;
    mode_t mode = STU_IREAD | STU_IWRITE;
    if (finfo.attr & ATT_RDONLY)
        mode &= ~STU_IWRITE;
    if (finfo.attr & ATT_FDIR)
        mode |= STU_IFDIR;
    else
        mode |= STU_IFREG;
    stat->st_mode = mode;
    stat->st_size = finfo.size;
    stat->st_atime = (finfo.date << 16) | finfo.time;
    stat->st_ctime = stat->st_mtime = stat->st_atime;
    return 0;
}

static offset_t FsalFatfsFtell(int idx)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_IDX2DIR(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return FileTell((file_obj_t *)fp->extension);
}

static int FsalFatfsChmod(char *path, mode_t mode)
{
    return -ENOSYS;
}

static int FsalFatfsFChmod(int idx, mode_t mode)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    return 0;
}

static int FsalFatfsUtime(char *path, time_t actime, time_t modtime)
{
    file_status_t res;
    file_info_t finfo;
    finfo.date = (modtime >> 16) & 0xffff;
    finfo.time = modtime & 0xffff;
    res = FileUtime(path, &finfo);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int FsalFatfsRewind(int idx)
{
    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    fsal_file_t *fp = FSAL_FILE_IDX2FILE(idx);
    if (FSAL_FILE_BAD_FILE(fp))
        return -1;
    file_status_t res = FileRewind((file_obj_t *)fp->extension);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int FsalFatfsRewindDir(int idx)
{
    if (FSAL_BAD_DIR_IDX(idx))
        return -1;
    fsal_dir_t *pdir = FSAL_IDX2DIR(idx);
    if (!pdir->flags)
        return -1;
    return FileRewindDir((dir_obj_t *)pdir->extension);
}

static int FsalFatfsRmdir(char *path)
{
    file_status_t res = FSTU_OK;
    res = FileRmDir(path);
    if (res != FSTU_OK)
        return -1;
    return 0;
}

static int FsalFatfsChdir(char *path)
{
    file_status_t res;
    dir_obj_t dir;
    res = FileOpenDir(&dir, path); // open the director
    if (res != FSTU_OK)
        return -1;
    FileCloseDir(&dir);
    return 0;
}

static int FsalFatfsIoctl(int fd, int cmd, void *arg)
{
    // no support
    return -ENOSYS;
}

static int FsalFatfsFctnl(int fd, int cmd, int arg)
{
    // no support
    return -ENOSYS;
}

static int FsalFatfsUnmount(char *path, uint64_t flags)
{
    int i;
    fatfs_extension_t *extension = &fatfs_extension;

    // check path whether is virtual path or pysical path
    if (FsalPathFind(path, 0) < 0 && FsalPathFindDevice(path) < 0)
    {
#if DEBUG_KERNEL
        KPrint("%s: path %s no found!\n", __func__, path);
#endif
        return -1;
    }
    // find disk info
    int solt = DiskSoltFindByPath(path);
    if (solt < 0)
    {
        KPrint("no found device %s!\n", path);
        return -1;
    }
    // translation pysical driver
    for (i = 0; i < DISK_MAX; i++)
    {
        if (driver_map[i] == solt)
            break;
    }
    // get pysical driver
    int pdrv = i;
    // if is unmount device
    if (fatfs_extension.ref[pdrv] < 1)
    {
        KPrint("device %s no mount!\n", path);
        return -1;
    }
    fatfs_extension.ref[pdrv]--;
    // unmount device
    if (fatfs_extension.ref[pdrv] == 0)
    {
        file_status_t res;
        char path[3] = {PDRV_TO_PATH(pdrv), ':'};
        const TCHAR *p = (const TCHAR *)path;
        res = FileUnmount(p);
        if (res != FSTU_OK)
        {
            KPrint("unmount device %s failed!\n", path);
            return -1;
        }
        KMemFree(fatfs_extension.fs[pdrv]);
        fatfs_extension.fs[pdrv] = NULL;
        if (FsalPathRemove((void *)p) < 0)
        {
#if DEBUG_KERNEL
            KPrint("%s: remove fsal path %s failed!\n", __func__, p);
#endif
            return -1;
        }
    }
    return 0;
}

fsal_t fatfs_fsal = {
    .name = "fatfs",
    .subtable = fatfs_subtable,
    .mkfs = FsalFatfsMkfs,
    .mount = FsalFatfsMount,
    .unmount = FsalFatfsUnmount,
    .open = FsalFatfsOpen,
    .close = FsalFatfsClose,
    .read = FsalFatfsRead,
    .write = FsalFatfsWrite,
    .lseek = FsalFatfsLseek,
    .opendir = FsalFatFsOpenDir,
    .closedir = FsalFatfsCloseDir,
    .readdir = FsalFatfsReadDir,
    .mkdir = FsalFatfsMkdir,
    .unlink = FsalFatfsUnlink,
    .ftell = FsalFatfsFTell,
    .rename = FsalFatfsReName,
    .ftruncate = FsalFatfsFtruncate,
    .fsync = FsalFatfsFsync,
    .status = FsalFatfsStatus,
    .chmod = FsalFatfsChmod,
    .fchmod = FsalFatfsFChomod,
    .utime = FsalFatfsUtime,
    .feof = FsalFatfsFEof,
    .ferror = FsalFatfsFError,
    .fsize = FsalFatfsFSize,
    .rewind = FsalFatfsRewind,
    .rewinddir = FsalFatfsRewindDir,
    .rmdir = FsalFatfsRmdir,
    .chdir = FsalFatfsChdir,
    .ioctl = FsalFatfsIoctl,
    .fctnl = FsalFatfsFctnl,
    .fstatus = FsalFatfsFstatus,
    .access = FsalFatfsAccess,
    .extension = (void *)&fatfs_extension};